package com.cy.pj.sys.service.aspect;

import com.cy.pj.common.annotation.RequiredLog;
import com.cy.pj.sys.pojo.SysLog;
import com.cy.pj.sys.service.SysLogService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Date;

/**
 * 在Spring AOP应用中,基于@Aspect注解描述的
 * 类型为一个切面类型,此类中要封装切入点及能行方法的定义
 * 1)切入点:要切入扩展业务逻辑的一些目标方法集合(例如使用了特点注解描述的方法)
 * 2)通知:封装了扩展业务逻辑的一个方法(spring)
 */
@Order(2)
@Slf4j
@Aspect
@Component
public class SysLogAspect {
//    private static final Logger log=
//            LoggerFactory.getLogger(SysLogAspect.class);
    /**
     * @Pointcut 注解用于定义切入点
     * @annotation (注解)为切入点表达式，后续由此注解描述的方法为切入
     * 点方法
     * 基于@Pointcut注解定义切入点,这里的@annotation为一种切入点表达式,
     * 表示由RequiredLog注解描述的方法为一个切入点方法,我们要在这们的方法上
     * 添加扩展业务逻辑
     */
    @Pointcut("@annotation(com.cy.pj.common.annotation.RequiredLog)")
    public void doLog(){}//此方法用于承载切入点的定义

    /**
     * 定义一个通知方法,这里使用@Around进行描述,表示在此方法内部可以调用
     * 目标方法,可以在目标方法执行之前或之后做一些拓展业务
     * @Around
     * @param jp 连接点,用于封装要执行的目标方法信息
     *           ProceedingJoinPoint 此类型参数只能定义在@Around中,
     * @return 这里的返回值一般为目标切入点方的执行结果
     * @throws Throwable
     */
    @Around("doLog()")
    public Object doAround(ProceedingJoinPoint jp)throws Throwable{
        System.out.println("@Around");
        long t1 = System.currentTimeMillis();
        log.info("method start {}",t1);
        try{
            //String targetCls=jp.getClass().getName();
            //System.out.println("targetCls= "+targetCls);
            //执行目标方法(切点方法中的某个方法)
            Object result = jp.proceed();//通过连接点调用目标方法method.invoke
            long t2=System.currentTimeMillis();


            log.info("method after {}",t2);
//            记录用户正常行为日志      当多次些日志时，会发生线程阻塞，当多次使用时会影响正常返回（不能立即返回值）
            doSaveLogInfo(jp,t2-t1,null);//springboot池中的线程
            return result;//目标业务方法的执行结果    如果没有分页查询,不能用void  return null,是不行的
        }catch(Throwable e){
            log.error("method error {}",System.currentTimeMillis());
            e.printStackTrace();
            long t3=System.currentTimeMillis();
            doSaveLogInfo(jp,t3-t1,e);
            throw e;

        }
    }
    @Autowired
    private SysLogService sysLogService;
    private void doSaveLogInfo(ProceedingJoinPoint jp,long time,Throwable e) throws NoSuchMethodException, JsonProcessingException {
        //1.获取用户行为日志
        //1.1获取登录用户信息
        String username="tony";//后续是系统的登录用户
        //1.2获取登录用户的ip地址
        String ip="192.168.100.11";//后续可以借助三方工具类
        //1.3获取用户操作
        //1.3.1获取方法所在类的字节码对象(目标对象对应的字节码对象)
        Class<?> cls=jp.getTarget().getClass();
        //1.3.2获取注解描述的方法对象(字节码对象，方法名，参数列表)
        Signature signature=jp.getSignature();
        // System.out.println("signature="+signature.getClass().getName());//MethodSignatureImpl
        MethodSignature methodSignature= (MethodSignature) signature;
        //Method targetMethod=methodSignature.getMethod();//cglib
        Method targetMethod=//cglib,jdk
                cls.getDeclaredMethod(methodSignature.getName(),methodSignature.getParameterTypes());
        //System.out.println("targetMethod="+targetMethod);
        //1.3.3获取RequiredLog注解（通过方法获取方法上面的注解）
        RequiredLog requiredLog=targetMethod.getAnnotation(RequiredLog.class);
        String operation=requiredLog.operation();
        //1.4获取方法声明信息
        String method=cls.getName()+"."+targetMethod.getName();
        //1.5获取方法执行时传入的实际参数
        Object[]args=jp.getArgs();//实际参数
        String params=new ObjectMapper().writeValueAsString(args);//转json
        //1.6获取状态信息
        Integer status=e==null?1:0;//1表示ok
        //1.7获取错误信息
        String error=e==null?"":e.getMessage();

        //2.封装用户行为日志
        SysLog sysLog=new SysLog();
        sysLog.setUsername(username);
        sysLog.setIp(ip);
        sysLog.setCreatedTime(new Date());
        sysLog.setOperation(operation);
        sysLog.setMethod(method);
        sysLog.setParams(params);
        sysLog.setStatus(status);
        sysLog.setError(error);
        sysLog.setTime(time);
        //将日志写入到数据库
        log.info("user log {}",new ObjectMapper().writeValueAsString(sysLog));
        sysLogService.saveLog(sysLog);
//        新的线程给插入日志，若每次都要新建一个线程将会发生服务器崩溃 一个线程被分配1M 会分配GC回收，会发生不间断卡顿
//        new Thread(){
//            @Override
//            public void run(){
//                sysLogService.saveLog(sysLog);
//            }
//        }.start();
    }
}
